package org.ftpscan;

import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Iterator;

import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.apache.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.DocumentSource;
import org.dom4j.io.SAXReader;

/**
 * Class for XML transformation.
 *
 * @author Nikolai Holub (nikolai.holub at gmail.com)
 */
public class XMLTransformer {
    private Logger log = Logger.getLogger(getClass().getPackage().getName());

    /**
     * Arguments:
     *  -x,--xml       XML file
     *  -s,--xsl       XSL file
     *  -r,--result    result file
     *
     * @param args command line arguments
     */
    public static void main(String[] args) {
        CommandLineParser parser = new PosixParser();
        Options options = new Options();

        Option xmlOption = new Option("x", "xml", true, "XML file");
        xmlOption.setRequired(true);

        Option xslOption = new Option("s", "xsl", true, "XSL file");
        xslOption.setRequired(true);

        Option resOption = new Option("r", "result", true, "result file");
        xslOption.setRequired(true);

        options.addOption(xmlOption);
        options.addOption(xslOption);
        options.addOption(resOption);

        try {
            CommandLine line = parser.parse(options, args);

            XMLTransformer transform = new XMLTransformer();
            transform.transform(
                line.getOptionValue("x"),
                line.getOptionValue("s"),
                line.getOptionValue("r")
            );
        } catch (ParseException e) {
            HelpFormatter formatter = new HelpFormatter();
            formatter.printHelp("XMLTransformer", options);
        }
    }

    /**
     * Transforms XML file.
     *
     * @param xmlFileName XML file name
     * @param xslFileName XSL file name
     * @param resFileName result file name
     */
    public void transform(
        String xmlFileName, String xslFileName, String resFileName) {

        SAXReader reader = new SAXReader();

        try {
            Document document = reader.read(xmlFileName);

            transform(document, xslFileName, resFileName);
        } catch (Throwable e) {
            log.error("exception", e);
        }
    }

    /**
     * Transforms XML document.
     *
     * @param document XML document
     * @param xslFileName XSL file name
     * @param resFileName result file name
     *
     * @throws Throwable if any exception occurs
     */
    public void transform(Document document, String xslFileName,
        String resFileName) throws Throwable {

        extend(document);

        TransformerFactory factory = TransformerFactory.newInstance();
        Transformer transformer = factory.newTransformer(
            new StreamSource(xslFileName)
        );

        log.info("transforming XML...");

        transformer.transform(
            new DocumentSource(document),
            new StreamResult(resFileName)
        );

        log.info("transformation to file " + resFileName + " finished");
    }

    /**
     * Extends document with additional attributes.
     * Generated attributes:
     *   scan_dir_url, scan_dir_decoded_url, ("files" element);
     *   url, dir_url ("file" elements).
     *
     * @param document document to extend
     * @throws Throwable if any exception occurs
     */
    private static void extend(Document document) throws Throwable {
        Element root = document.getRootElement();

        // generate attributes for "files" element
        String scanDir = root.attribute("scan_dir").getText();
        String host = root.attribute("host").getText();

        root.addAttribute("scan_dir_url", createURL(host, scanDir));
        root.addAttribute("scan_dir_decoded_url",
            createURLWithoutEncoding(host, scanDir));

        // generate attributes for "file" elements
        for (Iterator it = root.elementIterator(); it.hasNext();) {
            Element file = (Element) it.next();

            String name = file.attribute("name").getText();
            String dir = file.attribute("dir").getText();

            if (!dir.endsWith("/")) { // shouldn't happen
                dir += "/";
            }

            file.addAttribute("url", createURL(host, dir + name));
            file.addAttribute("dir_url", createURL(host, dir));
        }
    }

    /**
     * Creates FTP URL from the specified host and path.
     * Doesn't encode the path.
     *
     * @param host the host
     * @param path the path
     * @return FTP URL constructed from the specified host and path
     * @throws MalformedURLException
     *
     * @see java.net.URL
     */
    private static String createURLWithoutEncoding(String host, String path)
        throws MalformedURLException {
        return new URL("ftp", host, path).toString();
    }

    /**
     * Creates FTP URL from the specified host and path.
     * Encodes the path.
     *
     * @param host the host
     * @param path the path
     * @return FTP URL constructed from the specified host and path
     * @throws MalformedURLException
     *
     * @see java.net.URI
     */
    private static String createURL(String host, String path)
        throws URISyntaxException {
        return new URI("ftp", host, path, null).toString();
    }
}
